package algorithm.combination;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;


public class CombinationNonRecursive2 {
    int[] dataArray;

    public CombinationNonRecursive2(int[] dataArray) {
        this.dataArray = dataArray;
    }

    public static List<int[]> generate(int[] dataArray, int expectLength) {
        Stream.Builder<int[]> builder = Stream.builder();
        new CombinationNonRecursive2(dataArray).visit(expectLength, builder::accept);
        return builder.build().toList();
    }

    public void visit(int expectLength, Consumer<int[]> onVisit) {
        List<Integer> result = new ArrayList<>();
        Stack<CombinationNonRecursiveEvent> stack = new Stack<>();
        stack.push(createEvent("visit", Map.of("toVisitIndex", 0, "currentIndex", 0)));
        while (!stack.empty()) {
            CombinationNonRecursiveEvent event = stack.pop();
            switch (event.eventType) {
                case "visit" -> {
                    Map<?, ?> data = (Map<?, ?>) event.data();
                    int toVisitIndex = (int) (data).get("toVisitIndex");
                    int currentIndex = (int) (data).get("currentIndex");
                    if (currentIndex == expectLength) {
                        onVisit.accept(result.stream().mapToInt(it -> it).toArray());
                    } else if (toVisitIndex < dataArray.length) {
                        List<CombinationNonRecursiveEvent> events = new ArrayList<>();
                        events.add(createEvent("prepareData", dataArray[toVisitIndex]));
                        events.add(createEvent("visit", Map.of("toVisitIndex", toVisitIndex + 1,
                                "currentIndex", currentIndex + 1)));
                        events.add(createEvent("clearData", result.size()));
                        events.add(createEvent("visit", Map.of("toVisitIndex", toVisitIndex + 1,
                                "currentIndex", currentIndex)));
                        Collections.reverse(events);
                        events.forEach(stack::push);
                    }
                }
                case "prepareData" -> {
                    int data = (int) event.data();
                    result.add(data);
                }
                case "clearData" -> {
                    int resultSize = (int) event.data();
                    result.remove(resultSize);
                }
            }
        }
    }

    private CombinationNonRecursiveEvent createProcessEvent(Runnable runnable) {
        return createEvent("process", runnable);
    }

    private record CombinationNonRecursiveEvent(String eventType, Object data) {
    }

    CombinationNonRecursiveEvent createEvent(String eventType, Object data) {
        return new CombinationNonRecursiveEvent(eventType, data);
    }
}
